﻿using System;
using System.Collections.Generic;

namespace MvvmFx.Common.ViewModels.Commands
{
    /// <summary>
    /// Base Command class that exposes the public static methods to register an
    /// <see cref="ICommandHandler"/> to handle the execution of either a specified 
    /// instance of an <see cref="IViewModelCommand"/> or any instance of the specified
    /// type of <see cref="IViewModelCommand"/>.
    /// </summary>
    public abstract class Command
    {
        #region [ Private Members ]

        /// <summary>
        /// Instance Command bindings field.
        /// </summary>
        private static readonly Dictionary<IViewModelCommand, List<ICommandHandler>> instanceHandlers
            = new Dictionary<IViewModelCommand, List<ICommandHandler>>();

        /// <summary>
        /// Typed Command handlers field.
        /// </summary>
        private static readonly Dictionary<Type, List<ICommandHandler>> typeHandlers
            = new Dictionary<Type, List<ICommandHandler>>();

        #endregion

        #region [ Public Methods ]

        /// <summary>
        /// Add a command handler to be executed after a command of the specified type executes
        /// </summary>
        /// <param name="handler">The handler to be added.</param>
        /// <typeparam name="TCommand">The type of <see cref="IViewModelCommand"/>to be invoke the handler.</typeparam>
        public static void AddHandler<TCommand>(ICommandHandler handler)
            where TCommand : IViewModelCommand
        {
            Type commandType = typeof(TCommand);

            List<ICommandHandler> handlers;
            if (typeHandlers.TryGetValue(commandType, out handlers))
            {
                handlers.Add(handler);
            }
            else
            {
                handlers = new List<ICommandHandler> { handler };
                typeHandlers.Add(commandType, handlers);
            }
        }

        /// <summary>
        /// Remove a command handler 
        /// </summary>
        /// <param name="handler">The handler to be removed.</param>
        /// <typeparam name="TCommand">The command of type IViewModelCommand to remove its execute handler.</typeparam>
        public static void RemoveHandler<TCommand>(ICommandHandler handler)
        {
            Type commandType = typeof(TCommand);

            List<ICommandHandler> handlers;
            if (typeHandlers.TryGetValue(commandType, out handlers))
            {
                handlers.Remove(handler);

                if (handlers.Count == 0)
                {
                    typeHandlers.Remove(commandType);
                }
            }
        }

        /// <summary>
        /// Add a command handler to be executed after the specified instance of the command executes
        /// </summary>
        /// <param name="command">The command to be executed.</param>
        /// <param name="handler">The handler to be added.</param>
        public static void AddHandler(IViewModelCommand command, ICommandHandler handler)
        {
            List<ICommandHandler> handlers;
            if (instanceHandlers.TryGetValue(command, out handlers))
            {
                handlers.Add(handler);
            }
            else
            {
                handlers = new List<ICommandHandler> { handler };
                instanceHandlers.Add(command, handlers);
            }
        }

        /// <summary>
        /// Remove a command handler for the specfied command
        /// </summary>
        /// <param name="command">The command to remove it's command handler.</param>
        /// <param name="handler">The handler to be removed.</param>
        public static void RemoveHandler(IViewModelCommand command, ICommandHandler handler)
        {
            List<ICommandHandler> handlers;
            if (instanceHandlers.TryGetValue(command, out handlers))
            {
                handlers.Remove(handler);

                if (handlers.Count == 0)
                {
                    instanceHandlers.Remove(command);
                }
            }
        }

        #endregion

        #region [ Protected Methods ]

        /// <summary>
        /// Remove all the handlers to clean up the object references.
        /// Called by the Dispose method.
        /// </summary>
        protected virtual void RemoveHandlers()
        {
            List<ICommandHandler> handlers;
            if (instanceHandlers.TryGetValue((IViewModelCommand)this, out handlers))
            {
                handlers.Clear();

                instanceHandlers.Remove((IViewModelCommand)this);
            }
        }

        /// <summary>
        /// Notify handlers when the command is executed.
        /// </summary>
        protected void NotifyTypeHandlers(object parameter)
        {
            // Notify the typed handlers
            //
            Type commandType = this.GetType();

            List<ICommandHandler> handlers;
            if (typeHandlers.TryGetValue(commandType, out handlers))
            {
                // Execute each command handler
                handlers.ForEach(handler => handler.Execute(this as IViewModelCommand, parameter));
            }
        }

        /// <summary>
        /// Notify handlers when the command is executed.
        /// </summary>
        protected void NotifyInstanceHandlers(object parameter)
        {
            // Notify the instance handlers
            //
            var command = this as IViewModelCommand;
            if (command != null)
            {
                List<ICommandHandler> handlers;
                if (instanceHandlers.TryGetValue(command, out handlers))
                {
                    // Execute each command binding
                    handlers.ForEach(binding => binding.Execute(this as IViewModelCommand, parameter));
                }
            }
        }

        #endregion
    }
}